use std::{
    collections::HashSet,
    ops::{Deref, DerefMut},
    sync::Arc,
};

use crate::cache::{model::EntryDetail, ver::Version};

use super::{RepoType, SolveContext, SolveItem, TransationState};
use anyhow::Result;
use uuid::Uuid;
use varisat::{ExtendFormula, Lit};

struct ConflictItem {
    clause: Vec<isize>,
    pkg_index: isize,
    item: SolveItem,
}
impl ConflictItem {
    fn new(entry: EntryDetail, pkg_index: isize) -> Self {
        ConflictItem {
            item: SolveItem::new(entry),
            clause: Vec::new(),
            pkg_index,
        }
    }
    fn add_clause(&mut self, pkg_index: isize) {
        self.clause.push(pkg_index);
    }
}
impl Deref for ConflictItem {
    type Target = SolveItem;

    fn deref(&self) -> &Self::Target {
        &self.item
    }
}
impl DerefMut for ConflictItem {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.item
    }
}
impl SolveContext {
    async fn get_pkg_conflicts_by_uuid(
        &self,
        repo: RepoType,
        pkg_uuid: Uuid,
    ) -> Option<Vec<EntryDetail>> {
        match repo {
            RepoType::Repo(name) => {
                self.repos
                    .iter()
                    .find(|r| r.config.name == name)
                    .unwrap()
                    .get_pkg_conflicts_by_uuid(pkg_uuid)
                    .await
            }
            RepoType::Cmd => self.cmd_repo.get_pkg_conflicts_by_uuid(pkg_uuid).await,
            RepoType::Installed => {
                self.installed_repo
                    .get_pkg_conflicts_by_uuid(pkg_uuid)
                    .await
            }
        }
    }
    pub async fn solve_conflict(
        &mut self,
        (pkg_index, pkg_uuid, repo): (isize, Uuid, RepoType),
    ) -> Result<()> {
        let mut handles = Vec::new();
        if let Some(conflicts) = self.get_pkg_conflicts_by_uuid(repo, pkg_uuid).await {
            for entry in conflicts {
                handles.push(tokio::spawn(
                    self.clone()
                        .solve_conflict_item(ConflictItem::new(entry, pkg_index)),
                ));
            }
        };

        let mut indexs = Vec::new();
        for handle in handles {
            let mut r = handle.await??;
            indexs.append(&mut r);
        }
        let mut formula_lock = self.formula.lock().await;
        for index in indexs
            .iter()
            .map(|x| *x)
            .collect::<HashSet<isize>>()
            .iter()
            .filter(|x| **x != pkg_index)
        {
            formula_lock.add_clause(&[
                !Lit::from_dimacs(pkg_index as isize),
                !Lit::from_dimacs(*index as isize),
            ]);
        }
        drop(formula_lock);
        Ok(())
    }

    async fn solve_conflict_item(mut self, mut conflict: ConflictItem) -> Result<Vec<isize>> {
        self.cmd_check_conflict(&mut conflict).await;
        self.installed_check_conflict(&mut conflict).await?;
        self.repo_check_conflict(&mut conflict).await?;
        Ok(conflict.clause)
    }
    async fn cmd_check_conflict(&mut self, item: &mut ConflictItem) {
        if let Some(cmd) = self
            .cmd_repo
            .data
            .clone()
            .get_what_pkg_provide_entry_by_name(item.entry_name.clone())
            .await
        {
            for (pkg_uuid, entry_detail) in cmd {
                let cmd_ver = Version::from_entry_detail(&entry_detail);
                let cmd_flag = entry_detail.flags;
                if item.check_version(&cmd_ver, &cmd_flag) {
                    let index = self
                        .add_pkg_info(
                            Arc::new(entry_detail.pkg_name),
                            pkg_uuid,
                            RepoType::Cmd,
                            TransationState::Conflict(item.pkg_index),
                        )
                        .await;
                    item.add_clause(index);
                };
            }
        } else if item.entry_name.starts_with("/") {
            if let Some(cmd) = self
                .cmd_repo
                .data
                .clone()
                .get_what_pkg_provide_file_by_path(item.entry_name.clone())
                .await
            {
                for (pkg_uuid, file_detail) in cmd {
                    let index = self
                        .add_pkg_info(
                            Arc::new(file_detail.pkg_name),
                            pkg_uuid,
                            RepoType::Cmd,
                            TransationState::Conflict(item.pkg_index),
                        )
                        .await;
                    item.add_clause(index);
                }
            }
        }
    }
    async fn installed_check_conflict(&mut self, item: &mut ConflictItem) -> Result<()> {
        let mut conflict_installeds = Vec::new();
        if let Some(installed) = self
            .installed_repo
            .data
            .clone()
            .get_what_pkg_provide_entry_by_name(item.entry_name.clone())
            .await
        {
            for (pkg_uuid, entry) in installed {
                let installed_ver = Version::from_entry_detail(&entry);
                let installed_flag = entry.flags;
                let pkg_name = Arc::new(entry.pkg_name);
                if item.check_version(&installed_ver, &installed_flag) {
                    let index = self
                        .add_pkg_info(
                            pkg_name.clone(),
                            pkg_uuid,
                            RepoType::Installed,
                            TransationState::Conflict(item.pkg_index),
                        )
                        .await;
                    item.add_clause(index);
                    if !self
                        .installed_try_update_or_downgrade(pkg_name, item, &installed_ver)
                        .await?
                    {
                        conflict_installeds.push(index);
                    };
                };
            }
        } else if item.entry_name.starts_with("/") {
            if let Some(installed) = self
                .installed_repo
                .data
                .clone()
                .get_what_pkg_provide_file_by_path(item.entry_name.clone())
                .await
            {
                for (pkg_uuid, file_detail) in installed {
                    let index = self
                        .add_pkg_info(
                            Arc::new(file_detail.pkg_name),
                            pkg_uuid,
                            RepoType::Installed,
                            TransationState::Conflict(item.pkg_index),
                        )
                        .await;
                    item.add_clause(index);
                    conflict_installeds.push(index);
                }
            }
        }
        let mut assume_lock = self.assume.lock().await;
        assume_lock
            .conflict_installed
            .append(&mut conflict_installeds);
        drop(assume_lock);
        Ok(())
    }

    async fn repo_check_conflict(&mut self, item: &mut ConflictItem) -> Result<()> {
        if let Some(v) = self.which_repo_pkg_provide_entry(&item.entry_name).await? {
            for (repo, provides) in v {
                for (pkg_uuid, entry_detail) in provides {
                    let repo_ver = Version::from_entry_detail(&entry_detail);
                    let repo_flag = entry_detail.flags;
                    if item.check_version(&repo_ver, &repo_flag) {
                        let index = self
                            .add_pkg_info(
                                Arc::new(entry_detail.pkg_name),
                                pkg_uuid,
                                repo.clone(),
                                TransationState::Conflict(item.pkg_index),
                            )
                            .await;
                        item.add_clause(index);
                    };
                }
            }
        } else if item.entry_name.starts_with("/") {
            if let Some(v) = self.which_repo_pkg_provide_file(&item.entry_name).await? {
                for (repo, provides) in v {
                    for (pkg_uuid, file_detail) in provides {
                        let index = self
                            .add_pkg_info(
                                Arc::new(file_detail.pkg_name),
                                pkg_uuid,
                                repo.clone(),
                                TransationState::Conflict(item.pkg_index),
                            )
                            .await;
                        item.add_clause(index);
                    }
                }
            }
        }

        Ok(())
    }
}
